<!doctype html>
<html>
  <head>
    <meta charset="utf-8">

    <title>Navec — компактные эмбеддинги для русского языка</title>
    <meta name="title" content="Navec — компактные эмбеддинги для русского языка">
    <meta property="og:title" content="Navec — компактные эмбеддинги для русского языка">
    <meta property="twitter:title" content="Navec — компактные эмбеддинги для русского языка">

    <meta name="description" content="Часть проекта Natasha, коллекция предобученных эмбеддингов для русского языка, имеют компактный размер, высокое качество">
    <meta property="og:description" content="Часть проекта Natasha, коллекция предобученных эмбеддингов для русского языка, имеют компактный размер, высокое качество">
    <meta property="twitter:description" content="Часть проекта Natasha, коллекция предобученных эмбеддингов для русского языка, имеют компактный размер, высокое качество">

    <meta name="keywords" content="русский язык, эмбеддинги, glove, квантизация, embeddings, quantization">

    <meta property="og:type" content="website">
    <meta property="twitter:card" content="summary_large_image">

    <meta property="og:url" content="https://natasha.github.io/navec/">
    <meta property="twitter:url" content="https://natasha.github.io/navec/">

    <meta property="og:image" content="https://natasha.github.io/navec/images/preview.png">
    <meta property="twitter:image" content="https://natasha.github.io/navec/images/preview.png">

    <link rel="icon" href="/images/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/styles/bootstrap.min.css">
    <link rel="stylesheet" href="style.css">

    <!-- Google Tag Manager -->
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
      j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
      'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
      })(window,document,'script','dataLayer','GTM-P65FXVJ');</script>
    <!-- End Google Tag Manager -->
  </head>
  <body>
    <!-- Google Tag Manager (noscript) -->
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-P65FXVJ"
		      height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    <!-- End Google Tag Manager (noscript) -->

    <div class="container">
      <div class="row">
      	<div class="col-8">
      	  <p>
      	    <a href="/">
      	      <span class="hanging-arrow">←</span> Проект Natasha
      	    </a>
      	  </p>

      	  <h1>Navec — компактные эмбеддинги для русского языка</h1>
      	</div>
      </div>

      <div class="row">
      	<div class="col-6">
      	  <p>
	    <a href="https://github.com/natasha/navec">Библиотека Navec</a> — часть <a href="https://github.com/natasha">проекта Natasha</a>, коллекция предобученных эмбеддингов для русского языка. <a class="local" href="#evaluation">Качество решения сравнимо или выше</a>, чем у <a href="https://rusvectores.org/ru/models/">статических моделей от RusVectores</a>, размер в 5–6 раз меньше:
      	  </p>

	  <table id="hero" class="table table-borderless">
	    <thead>
	      <tr>
		<th></th>
		<th><a class="local" href="#evaluation">Качество</a></th>
		<th>Размер модели, МБ</th>
		<th>Размер словаря, ×10<sup>3</sup></th>
	      </tr>
	    </thead>
	    <tbody>
	      <tr>
		<th>Navec</th>
		<td>0.719</td>
		<td>50.6</td>
		<td>500</td>
	      </tr>

	      <tr>
		<th>RusVectores</th>
		<td>0.638–0.726</td>
		<td>220.6–290.7</td>
		<td>189–249</td>
	      </tr>
	    </tbody>
	  </table>

	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    В этой статье поговорим про старые добрые пословные эмбеддинги, совершившие революцию в NLP в 2013 году.
	  </p>
	</div>

	<div class="col-4">
	  <p class="note">
	    python − счастье + страдание = perl
	  </p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    Технология актуальна до сих пор. В проекте Natasha модели для <a href="https://github.com/natasha/slovnet#morphology">разбора морфологии</a>, <a href="https://github.com/natasha/slovnet#syntax">синтаксиса</a> и <a href="https://github.com/natasha/slovnet#ner">извлечения именованных сущностей</a> работают на пословных Navec-эмбеддингах, <a href="https://github.com/natasha/slovnet#evaluation">показывают качество выше других открытых решений</a>.
	  </p>
	</div>
      </div>

      <h2>RusVectores</h2>

      <div class="row">
	<div class="col-6">
	  <p>
	    Для русского языка принято использовать <a href="https://rusvectores.org/ru/models/">предобученные эмбеддинги от RusVectores</a>, у них есть неприятная особенность: в таблице записаны не слова, а пары «слово_POS-тег». Идея хорошая, для пары «печь_VERB» ожидаем вектор, похожий на «готовить_VERB», «варить_VERB», а для «печь_NOUN» — «изба_NOUN», «топка_NOUN».
	  </p>

	  <p>
	    На практике использовать такие эмбеддинги неудобно. Недостаточно разделить текст на токены, для каждого нужно как-то определить POS-тег. Таблица эмбеддингов разбухает. Вместо одного слова «стать», мы храним 6: 2 разумных «стать_VERB», «стать_NOUN» и 4 странных «стать_ADV», «стать_PROPN», «стать_NUM», «стать_ADJ». В таблице на 250&nbsp;000 записей 195&nbsp;000 уникальных слов.
	  </p>

	</div>
      </div>

      <a name="evaluation"></a>
      <h2>Качество</h2>

      <div class="row">
	<div class="col-6">
	  <p>
	    Оценим качество эмбеддингов на задаче семантической близости. Возьмём пару слов, для каждого найдём вектор-эмбеддинг, посчитаем косинусное сходство. Navec для похожих слов «чашка» и «кувшин» возвращет 0.49, для «фрукт» и «печь» — −0.0047. Соберём много пар с эталонными метками похожести, посчитаем корреляцию Спирмена с нашими ответами.
	  </p>
	</div>

      	<div class="col-4">
	  <p class="note">
	    Кроме семантической близости для оценки качества эмбеддингов используют <a href="https://aclweb.org/aclwiki/Analogy_(State_of_the_art)">задачу решения аналогий</a>. RusVectores публикует <a href="https://rusvectores.org/static/testsets/ru_analogy_tagged_PROPN.txt">переведённый датасет Google Analogies</a>. Прогнать на нём Navec пока не дошли руки.
	  </p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    Авторы RusVectores используют небольшой <a href="https://arxiv.org/abs/1801.06407">аккуратно проверенный и исправленный</a> тестовый список пар <a href="https://github.com/natasha/corus#load_simlex">SimLex965</a>. Добавим свежий яндексовый <a href="https://github.com/natasha/corus#load_toloka_lrwc">LRWC</a> и датасеты из <a href="https://russe.nlpub.org/downloads/">проекта RUSSE</a>: <a href="https://github.com/natasha/corus#load_russe_hj">HJ</a>, <a href="https://github.com/natasha/corus#load_russe_rt">RT</a>, <a href="https://github.com/natasha/corus#load_russe_ae">AE</a>, <a href="https://github.com/natasha/corus#load_russe_ae">AE2</a>:
	  </p>
	</div>

      	<div class="col-4">
	  <p class="note">
	    <a href="https://github.com/natasha/navec#evaluation">Таблица с разбивкой по датасетам</a> в репозитории Navec.
	  </p>
	</div>

      </div>

      <div class="row">
      	<div class="col-7">

	  <table id="eval" class="table table-borderless">
	    <thead>
      	      <tr>
      		<th></th>
      		<th></th>
      		<th scope="col">Среднее качество на 6 датасетах</th>
      		<th scope="col">Время загрузки, секунды</th>
      		<th scope="col">Размер модели, МБ</th>
      		<th scope="col">Размер словаря, ×10<sup>3</sup></th>
      	      </tr>
      	    </thead>

      	    <tbody>
      	      <tr>
		<th>Navec</th>
		<th><code>hudlit_12B_500K_300d_100q</code></th>
		<td><b>0.719</b></td>
		<td><b>1.0</b></td>
		<td><b>50.6</b></td>
		<td><b>500</b></td>
	      </tr>

	      <tr>
		<th></th>
		<th><code>news_1B_250K_300d_100q</code></th>
		<td>0.653</td>
		<td><b>0.5</b></td>
		<td><b>25.4</b></td>
		<td><b>250</b></td>
	      </tr>

	      <tr>
		<th>RusVectores</th>
		<th><code>ruscorpora_upos_cbow_300_20_2019</code></th>
		<td><b>0.692</b></td>
		<td><b>3.3</b></td>
		<td><b>220.6</b></td>
		<td>189</td>
	      </tr>

	      <tr>
		<th></th>
		<th><code>ruwikiruscorpora_upos_skipgram_300_2_2019</code></th>
		<td>0.691</td>
		<td>5.0</td>
		<td>290.0</td>
		<td>248</td>
	      </tr>

	      <tr>
		<th></th>
		<th><code>tayga_upos_skipgram_300_2_2019</code></th>
		<td><b>0.726</b></td>
		<td>5.2</td>
		<td>290.7</td>
		<td><b>249</b></td>
	      </tr>

	      <tr>
		<th></th>
		<th><code>tayga_none_fasttextcbow_300_10_2019</code></th>
		<td>0.638</td>
		<td>8.0</td>
		<td>2741.9</td>
		<td>192</td>
	      </tr>

	      <tr>
		<th></th>
		<th><code>araneum_none_fasttextcbow_300_5_2018</code></th>
		<td>0.664</td>
		<td>16.4</td>
		<td>2752.1</td>
		<td>195</td>
	      </tr>

	    </tbody>
	  </table>

      	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	     Качество <code>hudlit_12B_500K_300d_100q</code> сравнимо или лучше, чем у решений RusVectores, словарь больше в 2–3 раза, размер модели меньше в 5–6 раз. Как удалось получить такое качество и размер?
	  </p>
	</div>
      </div>

      <h2>Принцип работы</h2>

      <div class="row">
	<div class="col-6">
	  <p>
	    <code>hudlit_12B_500K_300d_100q</code> — <a href="https://nlp.stanford.edu/projects/glove/">GloVe-эмбеддинги</a> обученные на <a href="https://github.com/natasha/corus#load_librusec">145ГБ художественной литературы</a>. Архив с текстами возьмём из <a href="https://russe.nlpub.org/downloads/">проекта RUSSE</a>. Используем <a href="https://github.com/stanfordnlp/GloVe">оригинальную реализацию GloVe на C</a>, обернём её в <a href="https://github.com/natasha/navec/blob/master/navec/train/glove.py">удобный Python-интерфейс</a>.
	  </p>
	</div>
      </div>
	  
      <div class="row">
	<div class="col-6">
	  <p>
	    Почему не word2vec? Эксперименты на большом датасете быстрее с GloVe. Один раз считаем матрицу коллокаций, по ней готовим эмбеддинги разных размерностей, выбираем оптимальный вариант.
	  </p>
	</div>

	<div class="col-4">
	  <p class="note">
	    Оригинальная статья про GloVe: <a href="https://nlp.stanford.edu/pubs/glove.pdf">Global Vectors for Word Representation</a>
	  </p>
	</div>
      </div>
      
      <div class="row">
	<div class="col-6">
	  <p>
	    Почему не fastText? В проекте Natasha мы работаем с текстами новостей. В них мало опечаток, проблему OOV-токенов решает большой словарь. 250&nbsp;000 строк в таблице <code>news_1B_250K_300d_100q</code> покрывают 98% слов в новостных статьях.
	  </p>
	</div>

	<div class="col-4">
	  <p class="note">
	    Статья Давида Дале про компактный предобученный fastText для русского языка: <a href="https://habr.com/ru/post/489474/">Как сжать модель fastText в 100 раз</a>
	  </p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    Размер словаря <code>hudlit_12B_500K_300d_100q</code> — 500&nbsp;000 записей, он покрывает 98% слов в художественных текстах. Оптимальная размерность векторов — 300. Таблица 500&nbsp;000 × 300 из float-чисел занимает 578МБ, размер архива с весами <code>hudlit_12B_500K_300d_100q</code> в 12 раз меньше (48МБ). Дело в квантизации.
	  </p>
	</div>
      </div>

      <h2>Квантизация</h2>

      <div class="row">
	<div class="col-6">
	  <p>
	    Заменим 32-битные float-числа на 8-битные коды: [−∞, −0.86) — код 0, [−0.86, -0.79) — код 1, [-0.79, -0.74) — 2, …, [0.86, ∞) — 255. Размер таблицы уменьшится в 4 раз (143МБ):
	  <p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <pre>
Было:
-0.220 -0.071  0.320 -0.279  0.376  0.409  0.340 -0.329  0.400
 0.046  0.870 -0.163  0.075  0.198 -0.357 -0.279  0.267  0.239
 0.111  0.057  0.746 -0.240 -0.254  0.504  0.202  0.212  0.570
 0.529  0.088  0.444 <b>-0.005</b> <b>-0.003</b> -0.350 -0.001  0.472  0.635
-0.170  0.677  0.212  0.202 <b>-0.030</b>  0.279  0.229 -0.475 <b>-0.031</b>

Стало:
    63    105    215     49    225    230    219     39    228
   143    255     78    152    187     34     49    204    198
   163    146    253     58     55    240    188    191    246
   243    155    234    <b>127</b>    <b>127</b>     35    128    237    249
    76    251    191    188    <b>118</b>    207    195     18    <b>118</b></pre>
	</div>

	<div class="col-4">
	  <p class="note">
	    Данные огрубляются, разные значения -0.005 и -0.003 заменяет один код 127, -0.030 и -0.031 — 118
	  </p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    Заменим кодом не одно, а 3 числа. Кластеризуем все тройки чисел из таблицы эмбеддингов <a href="https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_k-%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D0%B8%D1%85">алгоритмом k-means</a> на 256 кластеров, вместо каждой тройки будем хранить код от 0 до 255. Таблица уменьшится ещё в 3 раза (48МБ). Navec использует <a href="http://yusukematsui.me/project/pqkmeans/pqkmeans.html">библиотеку PQk-means</a>, она разбивает матрицу на 100 колонок, каждую кластеризует отдельно, качество на синтетических тестах падёт на 1 процентный пункт.
	  </p>
	</div>

	<div class="col-4">
	  <p class="note">
	    
	  </p>
	  <p class="note">
	    Понятно про квантизацию в статье Chris McCormick <a href="http://mccormickml.com/2017/10/13/product-quantizer-tutorial-part-1/">Product Quantizers for k-NN Tutorial</a>
	  </p>
	</div>
      </div>

      <div class="row">
	<div class="col-6">
	  <p>
	    Квантованные эмбеддинги проигрывают обычным по скорости. Сжатый вектор перед использованием нужно распаковать. Аккуратно реализуем процедуру, <a href="https://github.com/natasha/navec/blob/master/navec/pq.py#L40-L43">применим Numpy-магию</a>, в PyTorch <a href="https://github.com/natasha/slovnet/blob/master/slovnet/model/emb.py#L29-L39">используем <code>torch.gather</code></a>. В Slovnet NER доступ к таблице эмбеддингов занимает 0.1% от общего времени вычислений.
	  </p>
	</div>
      </div>

      <h2>Использование</h2>

      <div class="row">
	<div class="col-6">
	  <p>
	    <a href="https://github.com/natasha/navec#downloads">Список предобученных моделей</a>, <a href="https://github.com/natasha/navec#installation">инструкция по установке</a> — в <a href="https://github.com/natasha/navec">репозитории Navec</a>.
	  </p>

	  <p>
	    Модуль <code>NavecEmbedding</code> из <a href="https://github.com/natasha/slovnet">библиотеки Slovnet</a> интегрирует Navec в PyTorch-модели:
	  </p>

	  <pre>
>>> import torch

>>> from navec import Navec
>>> from slovnet.model.emb import NavecEmbedding

>>> path = 'hudlit_12B_500K_300d_100q.tar'  # 51MB
>>> navec = Navec.load(path)  # ~1 sec, ~100MB RAM

>>> words = ['навек', '&lt;unk>', '&lt;pad>']
>>> ids = [navec.vocab[_] for _ in words]

>>> emb = NavecEmbedding(navec)
>>> input = torch.tensor(ids)

>>> emb(input)  # 3 x 300
tensor([[ 4.2000e-01,  3.6666e-01,  1.7728e-01,
        [ 1.6954e-01, -4.6063e-01,  5.4519e-01,
        [ 0.0000e+00,  0.0000e+00,  0.0000e+00,
	  ...</pre>

	</div>
      </div>

      <div id="footer">
	<div class="row">
      	  <div class="col-2">
      	    <img src="/images/author.jpg" class="rounded img-fluid" alt="Александр Кукушкин">
      	  </div>
      	  <div class="col-4">
      	    Александр Кукушкин, июнь 2020 года
      	    <ul>
      	      <li>
		<a href="mailto:alex@alexkuk.ru">alex@alexkuk.ru</a>
	      </li>

      	      <li>
		<a href="https://t.me/alexkuk">
		  <img class="inline" src="/images/social/tg.svg" /> alexkuk
		</a>
	      </li>

      	      <li>
		<a href="https://github.com/kuk">
		  <img class="inline" src="/images/social/gh.svg" /> kuk
		</a>
	      </li>
      	    </ul>

	    <a href="https://lab.alexkuk.ru/">Лаборатория анализа данных Александра Кукушкина</a>
	      <p>
		Лаборатория разрабатывает сервисы и коробочные продукты с использованием технологии Natasha, оказывает услуги анализа данных для российских компаний.
	      </p>

	  </div>
	</div>

	</div>
      </div>

    </div>

    <script src="/scripts/popper.min.js" type="text/javascript"></script>
    <script src="/scripts/bootstrap.min.js" type="text/javascript"></script>
  </body>
</html>
